package cn.newgrand.netcall.offlinefiletransfer;

import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.util.StringUtils;

import cn.newgrand.smackx.packet.OfflineStreamInitiation;

import java.io.*;
import java.net.Socket;
import java.net.URLConnection;

public class OfflineCreateSocks5 {

    protected static final String NAMESPACE = "http://jabber.org/protocol/bytestreams";

    /**
     * The number of connection failures it takes to a streamhost for that particular streamhost
     * to be blacklisted. When a host is blacklisted no more connection attempts will be made to
     * it for a period of 2 hours.
     */
    private static final int CONNECT_FAILURE_THRESHOLD = 2;

    private final Connection connection;
    
    public OfflineCreateSocks5(Connection connection) {
		// TODO Auto-generated constructor stub
    	this.connection = connection;
	}

    InputStream negotiateIncomingStream(String proxyIP,String proxyPort,String sessionID,String form,String to,String initiator,String md5,String dataString)
    throws XMPPException, IOException {
    	
		OfflineStreamInitiation osi = new OfflineStreamInitiation();
		osi.setSesssionID(sessionID);
		osi.setStatus("receive");

		OfflineStreamInitiation.File siFile = new OfflineStreamInitiation.File("0", 1);
		siFile.setTargetJID("");
        siFile.setDesc("");
        osi.setFile(siFile);
        
        osi.setType(IQ.Type.SET);
        osi.setFrom(form);
        osi.setTo(to);
        PacketCollector collectorInit = connection.createPacketCollector(new PacketIDFilter(
        		osi.getPacketID()));
        connection.sendPacket(osi);

        IQ serverResponseInit = (IQ) collectorInit.nextResult(SmackConfiguration
                .getPacketReplyTimeout());
        collectorInit.cancel();
        
		Socket selectedHost;
		try {
		    // select appropriate host
		    selectedHost = selectHost( proxyIP, proxyPort, sessionID, form, to);
		}
		catch (IOException ex) {
			throw new IOException(ex.getMessage());
		}
		
		// send used-host confirmation
        cn.newgrand.smackx.packet.Bytestream activate = createByteStreamActivate(sessionID,form, to, initiator, md5, dataString);
        
        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(
                activate.getPacketID()));
        connection.sendPacket(activate);

        IQ serverResponse = (IQ) collector.nextResult(SmackConfiguration
                .getPacketReplyTimeout());
        collector.cancel();
        if (!serverResponse.getType().equals(IQ.Type.RESULT)) {
        	selectedHost.close();
            return null;
        }
		
		try {
		    PushbackInputStream stream = new PushbackInputStream(
		    		selectedHost.getInputStream());
		    int firstByte = stream.read();
		    stream.unread(firstByte);
		    return stream;
		}
		catch (IOException e) {
		    throw new XMPPException("Error establishing input stream", e);
		}
	
	}
    
    
    public Socket selectHost(String proxyIP,String proxyPort,String sessionID,String form,String to) throws IOException {
            // establish socket
    	Socket socket;
            try {
                socket = new Socket(proxyIP, Integer.parseInt(proxyPort));
                establishSOCKS5ConnectionToProxy(socket, createDigest(
                		sessionID, form, to));
              
            }
            catch (IOException e) {
                e.printStackTrace();
                socket = null;
            }
        if (socket == null || !socket.isConnected()) {
            String errorMessage = "Could not establish socket with any provided host";
            throw new IOException(errorMessage);
        }

        return socket;
    }

    private int getConnectionFailures(String address) {
        return 0; //transferNegotiatorManager.getConnectionFailures(address);
    }

    /**
     * Creates the digest needed for a byte stream. It is the SHA1(sessionID +
     * initiator + target).
     *
     * @param sessionID The sessionID of the stream negotiation
     * @param initiator The inititator of the stream negotiation
     * @param target    The target of the stream negotiation
     * @return SHA-1 hash of the three parameters
     */
    private String createDigest(final String sessionID, final String initiator,
            final String target) {
        return StringUtils.hash(sessionID + StringUtils.parseName(initiator)
                + "@" + StringUtils.parseServer(initiator) + "/"
                + StringUtils.parseResource(initiator)
                + StringUtils.parseName(target) + "@"
                + StringUtils.parseServer(target) + "/"
                + StringUtils.parseResource(target));
    }
    
    private static cn.newgrand.smackx.packet.Bytestream createByteStreamActivate(final String sessionID,
            final String from, final String to, final String target, final String md5,final String dataString)
    {
        cn.newgrand.smackx.packet.Bytestream activate = new cn.newgrand.smackx.packet.Bytestream(sessionID);
        activate.setMode(null);
        activate.setToActivate(target,md5,"receive",dataString);
        activate.setFrom(from);
        activate.setTo(to);
        activate.setType(IQ.Type.SET);
        return activate;
    }

    public String[] getNamespaces() {
        return new String[]{NAMESPACE};
    }

    private void establishSOCKS5ConnectionToProxy(Socket socket, String digest)
            throws IOException {

        byte[] cmd = new byte[3];

        cmd[0] = (byte) 0x05;
        cmd[1] = (byte) 0x01;
        cmd[2] = (byte) 0x00;

        OutputStream out = new DataOutputStream(socket.getOutputStream());
        out.write(cmd);

        InputStream in = new DataInputStream(socket.getInputStream());
        byte[] response = new byte[2];

        in.read(response);

        cmd = createOutgoingSocks5Message(1, digest);
        out.write(cmd);
        createIncomingSocks5Message(in);
    }

    static String createIncomingSocks5Message(InputStream in)
            throws IOException {
        byte[] cmd = new byte[5];
        in.read(cmd, 0, 5);

        byte[] addr = new byte[cmd[4]];
        in.read(addr, 0, addr.length);
        String digest = new String(addr);
        in.read();
        in.read();

        return digest;
    }

    static byte[] createOutgoingSocks5Message(int cmd, String digest) {
        byte addr[] = digest.getBytes();

        byte[] data = new byte[7 + addr.length];
        data[0] = (byte) 5;
        data[1] = (byte) cmd;
        data[2] = (byte) 0;
        data[3] = (byte) 0x3;
        data[4] = (byte) addr.length;

        System.arraycopy(addr, 0, data, 5, addr.length);
        data[data.length - 2] = (byte) 0;
        data[data.length - 1] = (byte) 0;

        return data;
    }

    public void cleanup() {

    }

    public OutputStream createOutgoingStream(String streamID, String initiator,
            String target, boolean isOffline) throws XMPPException{
    	return null;
    }
}

